#include "_DoFontId.h"
#include "DoName.h"
#include "DoDictionary.h"
#include "DoString.h"
#include "DoArray.h"
#include "SkTypeface.h"
#include "SkStream.h"
#include "SkFontHost.h"
#include "MatrixUtils.h"

static const int cHeaderByteSize = 12;
static const int cTableDirByteSize = 16;

static void writeBytes(unsigned char *buffer, int offset, const unsigned char *src, size_t srclen) {
	for(size_t i = 0; i < srclen; i++) {
		buffer[offset + i] = src[i];
	}
}

static void writeUInt16(unsigned char *buffer, int offset, int value) {
	buffer[offset + 0] = (unsigned char) ((value >> 8) & 0xff);
	buffer[offset + 1] = (unsigned char) ((value) & 0xff);
}

static void writeFixed32(unsigned char* buffer, int offset, float value) {
	int part = (int)value;
	writeUInt16(buffer, offset, part);
	part = (int) ((value - part) * 65536.0);
	writeUInt16(buffer, offset + 2, part);
}

static void writeUInt32(unsigned char *buffer, int offset, int value) {
	buffer[offset + 0] = (unsigned char) ((value >> 24) & 0xff);
	buffer[offset + 1] = (unsigned char) ((value >> 16) & 0xff);
	buffer[offset + 2] = (unsigned char) ((value >> 8) & 0xff);
	buffer[offset + 3] = (unsigned char) ((value) & 0xff);
}

static void writeUInt8(unsigned char *buffer, int offset, int value) {
	buffer[offset + 0] = (unsigned char) ((value) & 0xff);
}

static int readUInt16(const unsigned char *buffer, int offset) {
	int value = 0;
	value += buffer[offset + 0];
	value <<= 8;
	value += buffer[offset + 1];	
	return value;
}

static int readUInt32(const unsigned char *buffer, int offset) {
	int value = 0;
	value += buffer[offset + 0];
	value <<= 8;
	value += buffer[offset + 1];	
	value <<= 8;
	value += buffer[offset + 2];
	value <<= 8;
	value += buffer[offset + 3];
	return value;
}

static float readFixed32(const unsigned char *buffer, int offset) {
	float retval = 0;
	retval = readUInt16(buffer, offset);
	retval += (readUInt16(buffer, offset + 2) / 65536.0);
	return retval;
}

class TTFTable : public _DoObject
{
public:
	int mOffset;
	int mLength;
	int mChecksum;
	std::string mName;
	
	TTFTable() 
	: mOffset(0)
	, mLength(0)
	, mChecksum(0)
	, mName() {
	}
	
	static int recalcTableOffset(_DoCompositeObject &array, TTFTable *t) {
		size_t size = cHeaderByteSize + array.size() * cTableDirByteSize;
		for(size_t i = 0; i < array.size(); i++) {
			TTFTable *p0 = dynamic_cast< TTFTable * >(array.get(i).get());
			if (p0 == t) 
				break;
			size += p0->mLength;
		}
		return size;
	}
	
	int read(const unsigned char *buffer, int offset)  {
		int cb = 0;

		ostringstream oss;
		for (int i = 0; i < 4; i++) oss.put(buffer[offset + i]);
		mName = oss.str();
		cb += 4;
		mChecksum = readUInt32(buffer, offset + cb);
		cb += 4;
		mOffset = readUInt32(buffer, offset + cb);
		cb += 4;
		mLength = readUInt32(buffer, offset + cb);
		cb += 4;

		return cb;
	}
	
	virtual void repackDir(_DoCompositeObject &array, ostringstream &baos) {
		int offset = 0;
		unsigned char blob[cTableDirByteSize];
		
		writeBytes(blob, offset, (const unsigned char *)mName.data(), 4);
		offset += 4;
		writeUInt32(blob, offset, mChecksum);
		offset += 4;
		int newOffset = recalcTableOffset(array, this);
		writeUInt32(blob, offset, newOffset);
		offset += 4;
		writeUInt32(blob, offset, mLength);
		offset += 4;
		baos.write((char *)blob, cTableDirByteSize);
	}
	
	virtual void repackContent(ostringstream &baos, const unsigned char *src) {
		baos.write((const char *)(src + mOffset), mLength);
	}
};

static const short cNameHeaderSize = 6;
static const short cNameRecordSize = 12;

class NameTable : public TTFTable
{
public:
	std::string mContent;
	std::string mFontName;

	NameTable() : TTFTable()
	, mContent()
	, mFontName() {
		mName = "name";
	}
	
	class NameRecord 
	{
	public:
		short mNameId;
		short mStringLength;
		short mStringOffset;
		std::string mString;
		
		void setString(const std::string &sin) {
			mString = sin;
			mStringLength = mString.size() * 2;
		}
		
		void toBuffer(ostringstream &baos) {
			short mPlatformId = 3;
			short mPlatformEncodingId = 1;
			short mLanguageId = 1033;
		
			int offset = 0;
			unsigned char buffer[cNameRecordSize];
			writeUInt16(buffer, offset, mPlatformId);
			offset += 2;
			writeUInt16(buffer, offset, mPlatformEncodingId);
			offset += 2;
			writeUInt16(buffer, offset, mLanguageId);
			offset += 2;
			writeUInt16(buffer, offset, mNameId);
			offset += 2;
			writeUInt16(buffer, offset, mStringLength);
			offset += 2;
			writeUInt16(buffer, offset, mStringOffset);
			offset += 2;
			baos.write((char *)buffer, cNameRecordSize);
		}
		
		void writeUTF16(ostringstream &baos) {
			for (size_t i = 0; i < mString.size(); i++) {
				baos.put(0);
				baos.put(mString[i]);
			}
		}
	};
protected:
	void toBuffer() {
		int offset = 0;
		ostringstream baos;
		
		{
			const short formatSelector = 0;
			const short numberOfNameRecords = 3;
			const short offsetToStartOfStringStorage = (short) (numberOfNameRecords * cNameRecordSize + cNameHeaderSize);
		
			unsigned char buffer[cNameHeaderSize];
			writeUInt16(buffer, offset, formatSelector);
			offset += 2;
			writeUInt16(buffer, offset, numberOfNameRecords);
			offset += 2;
			writeUInt16(buffer, offset, offsetToStartOfStringStorage);
			offset += 2;
			baos.write((char *)buffer, cNameHeaderSize);
		}
		
		std::string copyright = "Copyright Notice";
		offset = 0;
		NameRecord name;
		{
			name.setString(copyright);
			name.mNameId = 0;
			name.mStringOffset = (short) offset;
			name.toBuffer(baos);
			offset += name.mStringLength;
		}
		{
			name.setString(mFontName);
			name.mNameId = 1;
			name.mStringOffset = (short) offset;
			name.toBuffer(baos);
			offset += name.mStringLength;
		}
		{
			name.setString(mFontName);
			name.mNameId = 4;
			name.mStringOffset = (short) offset;
			name.toBuffer(baos);
			offset += name.mStringLength;
		}
		{
			name.setString(copyright);
			name.writeUTF16(baos);
			name.setString(mFontName);
			name.writeUTF16(baos);
			name.writeUTF16(baos);
		}
		
		mContent = baos.str();
		mLength = mContent.size();
	}
	
	virtual void repackDir(_DoCompositeObject &array, ostringstream &baos) {
		int offset = 0;
		unsigned char blob[cTableDirByteSize];
		
		toBuffer();
		
		writeBytes(blob, offset, (const unsigned char *)mName.data(), 4);
		offset += 4;
		writeUInt32(blob, offset, 0);
		offset += 4;
		int newOffset = recalcTableOffset(array, this);
		writeUInt32(blob, offset, newOffset);
		offset += 4;
		writeUInt32(blob, offset, mLength);
		offset += 4;
		baos.write((char *)blob, cTableDirByteSize);
	}
	
	virtual void repackContent(ostringstream &baos, const unsigned char *src) {
		baos.write(mContent.data(), mLength);
	}
};

_DoFontId::_DoFontId()
: _DoObject()
{
	mObjType = eFontId;
}

_DoFontId::~_DoFontId()
{
}
	
void _DoFontId::repackFont(CRefPtr< _DoObject > dict, const char* fid)
{
	DoDictionary *ftDict = toDict(dict);
	DoName *name = toName(ftDict->get(CRefPtr< _DoObject >(new DoName("FontName"))));
	DoArray *sfnts = toArray(ftDict->get(CRefPtr< _DoObject >(new DoName("sfnts"))));
	
	DoString *blob = toString(sfnts->get(0));
	//blob->_debug_hex("font_0.ttf");
	
	int offset = 0;
	_DoCompositeObject array;
	std::string strblob = blob->getHex();
	const unsigned char *buffer = (const unsigned char *) strblob.data();
	
	//read & parse
	int fontVersion = readFixed32(buffer, offset);
	offset += 4;
	int numOfTables = readUInt16(buffer, offset);
	offset += 2;
	int searchRange = readUInt16(buffer, offset);
	offset += 2; // searchRange
	int entrySelector = readUInt16(buffer, offset);
	offset += 2; // entrySelector;
	int rangeShift = readUInt16(buffer, offset);
	offset += 2;  // rangeShift
	for( int i = 0; i < numOfTables; i++) {
		TTFTable *p0 = new TTFTable();
		offset += p0->read(buffer, offset);
		array.add(CRefPtr< _DoObject >(p0));
	}
	for( int i = 0; i < numOfTables; i++) {
		TTFTable *p0 = dynamic_cast< TTFTable * >(array.get(i).get());
		if (p0->mName.compare("name") == 0) {
			break;
		}
		if (p0->mName.compare("name") > 0) {
			NameTable *nt = new NameTable();
			nt->mFontName = name->getText();
			
			array.insert(i, CRefPtr< _DoObject >(nt));
			numOfTables++;
			break;
		}
	}
	
	//write and repack
	ostringstream baos;
	
	offset = 0;
	unsigned char head[cHeaderByteSize];
	writeFixed32(head, offset, fontVersion);
	offset += 4;
	writeUInt16(head, offset, numOfTables);
	offset += 2;
	writeUInt16(head, offset, searchRange);
	offset += 2;
	writeUInt16(head, offset, entrySelector);
	offset += 2;
	writeUInt16(head, offset, rangeShift);
	offset += 2;
	baos.write((char *)head, cHeaderByteSize);
	
	for(int i = 0; i < numOfTables; i++) {
		TTFTable *p0 = dynamic_cast< TTFTable * >(array.get(i).get());
		p0->repackDir(array, baos);
	}
	
	for(int i = 0; i < numOfTables; i++) {
		TTFTable *p0 = dynamic_cast< TTFTable * >(array.get(i).get());
		p0->repackContent(baos, buffer);
	}
	
	blob->setHex(baos.str());
	
	//ostringstream oss;
	//oss << fid << ".ttf";
	//blob->_debug_hex(oss.str());
}

SkTypeface * _DoFontId::getFont(CRefPtr< _DoObject > dict)
{
	DoDictionary *ftDict = toDict(dict);
	SkTypeface *font = 0;
	
	if (ftDict->containsKey(newName("_Path"))) {
		CRefPtr< _DoObject > value = ftDict->get(newName("_Path"));
		font = SkFontHost::CreateTypefaceFromFile(value->getText());
	} else {
		DoArray *sfnts = toArray(ftDict->get(newName("sfnts")));
		
		DoString *blob = toString(sfnts->get(0));
		std::string strblob = blob->getHex();
		const unsigned char *buffer = (const unsigned char *) strblob.data();
		
		SkMemoryStream *ms = new SkMemoryStream();
		ms->setMemory(buffer, strblob.size(), true);
		font = SkFontHost::CreateTypefaceFromStream(ms);
	}
	return font;
}
	
CRefPtr< _DoObject > _DoFontId::defaultFont()
{
	DoDictionary *ftDict = new DoDictionary("defaultfont");
	CRefPtr< _DoObject > key;
	CRefPtr< _DoObject > value;
	
	key = _DoObject::newName("FontType");
	value = _DoObject::newNumber(42);
	ftDict->put(key, value);
	
	key = _DoObject::newName("FontName");
	value = _DoObject::newName("default");
	ftDict->put(key, value);
	
	key = _DoObject::newName("FontMatrix");
	value = MatrixUtils::identity();
	ftDict->put(key, value);
	
	key = _DoObject::newName("_Path");
	value = _DoObject::newString("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf");
	ftDict->put(key, value);
	
	return CRefPtr< _DoObject >(ftDict);
}
